﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.contexts;
using System.Diagnostics;
using System.Reflection;

namespace xplang.abstractions
{

  [DebuggerDisplay("({type}){name}")]
  public class NativeClassFor : NativeClass
  {
    public NativeClassFor(Context context, String name, Type type)
      : base(context, name)
    {
      this.type = type;
      genericParams = new GenericParamDecl[] { };
      if (type.BaseType == typeof(Object))
        inheritance = new ClassRef[] { };
      else
        inheritance = new ClassRef[] { mappedType(type.BaseType) };
      registerMembers();
    }

    public readonly Type type;

    public override void registerMembers()
    {
      FieldInfo[] fi = type.GetFields();
      int n = fi.Length;
      for (int i = 0; i < n; i++)
      {
        ClassRef t = mappedType(fi[i].FieldType);
        attributes.Add(new VariableForField(Modifier.None, t, fi[i]));
      }

      PropertyInfo[] pi = type.GetProperties();
      n = pi.Length;
      for (int i = 0; i < n; i++)
      {
        ClassRef t = mappedType(pi[i].PropertyType);
        attributes.Add(new VariableForProperty(Modifier.None, t, pi[i]));
      }

      MethodInfo[] mis = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
      n = mis.Length;
      for (int i = 0; i < n; i++)
      {
        MethodInfo mi = mis[i];
        if (!mi.IsSpecialName && !mi.Name.StartsWith("get_") && !mi.Name.StartsWith("set_"))
        {
          ClassRef returnType = mappedType(mi.ReturnType);
          ParameterInfo[] parinfo = mi.GetParameters();
          int k = parinfo.Length;
          Variable[] parameters = new Variable[k];
          for (int j = 0; j < k; j++)
          {
            ClassRef t = mappedType(parinfo[j].ParameterType);
            parameters[j] = new Variable(Modifier.None, parinfo[j].Name, t, null);
          }
          ImportedClassMethod method = new ImportedClassMethod(this, Modifier.None, returnType, mi, parameters);
          methods.Add(method);
          method.registerImplementation(this, invokeMethod);
        }
      }
    }

    private Value invokeMethod(Value[] heap, ref FlowState state)
    {
      return null;
    }

    private ClassRef mappedType(Type type)
    {
      if (type == typeof(int))
        return xplang.rtl.RTL.intClass.getRef(null);
      if (type == typeof(Int16))
        return xplang.rtl.RTL.intClass.getRef(null);
      if (type == typeof(Int32))
        return xplang.rtl.RTL.intClass.getRef(null);
      if (type == typeof(Int64))
        return xplang.rtl.RTL.intClass.getRef(null);
      if (type == typeof(Double))
        return xplang.rtl.RTL.doubleClass.getRef(null);
      if (type == typeof(String))
        return xplang.rtl.RTL.stringClass.getRef(null);
      if (type == typeof(string))
        return xplang.rtl.RTL.stringClass.getRef(null);
      if (type == typeof(bool))
        return xplang.rtl.RTL.booleanClass.getRef(null);
      if (type == typeof(Boolean))
        return xplang.rtl.RTL.booleanClass.getRef(null);
      throw new Exception("Tipo de dado não suportado");
    }

    public override object newInstance()
    {
      ConstructorInfo c = type.GetConstructor(new Type[] { });
      return c.Invoke(new object[] { });
    }

    public override Value getAttributeValue(Value _this, Variable variable)
    {
      Object v=((VariableFor)variable).Get(_this.pointer);
      if (variable.type == xplang.rtl.RTL.intClass.getRef(null))
        v = Convert.ToInt64(v);
      return new Value(variable.type, v);
    }

    public override void setAttributeValue(Value _this, Variable variable, Value obj)
    {
      ((VariableFor)variable).Set(_this.pointer, obj.pointer);
    }

    private abstract class VariableFor : Variable
    {
      public VariableFor(String name, ClassRef type)
        : base(Modifier.None, name, type, null)
      {
      }

      public abstract Object Get(Object _this);
      public abstract void Set(Object _this, Object value);
    }

    private class VariableForField : VariableFor
    {
      public VariableForField(Modifier modifier, ClassRef type, FieldInfo field)
        : base(field.Name, type)
      {
        this.field = field;
      }
      public readonly FieldInfo field;


      public override object Get(object _this)
      {
        return field.GetValue(_this);
      }

      public override void Set(object _this, object value)
      {
        field.SetValue(_this, value);
      }
    }

    private class VariableForProperty : VariableFor
    {
      public VariableForProperty(Modifier modifier, ClassRef type, PropertyInfo prop)
        : base(prop.Name, type)
      {
        this.prop = prop;
      }
      public readonly PropertyInfo prop;

      public override object Get(object _this)
      {
        return prop.GetValue(_this, new object[] { });
      }

      public override void Set(object _this, object value)
      {
        prop.SetValue(_this, value, new object[] { });
      }
    }

    public class ImportedClassMethod : NativeClassMethod
    {
      public ImportedClassMethod(ClassDecl declaringClass, Modifier modifier, ClassRef returnType, MethodInfo method, Variable[] parameters)
        : base(declaringClass, modifier, returnType, method.Name, parameters)
      {
        this.method = method;
      }

      public readonly MethodInfo method;

    }
  }

}
